package emu.decvices.cartridge;

import emu.decvices.cartridge.mappers.Mapper;
import emu.decvices.cartridge.mappers.MapperFactory;
import emu.decvices.cartridge.mappers.RomHeader;

import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.io.Serializable;

/**
 * 模拟卡带数据
 */
public class Cartridge implements Serializable {

    //程序存储空间
    protected byte[] prgData;
    protected byte[] chrData;

    //映射器.还不能确定PRG和CHR是否为用的同一个映射器，暂时分别在不同存储区创建
    private Mapper mapper;

    private RomHeader romHeader;//ROM的头信息

    private Mirror mirror = Mirror.HORIZONTAL;//镜像方式

    private String romFileName;//rom文件名


    public Cartridge(File romFile) {
        loadRom(romFile);//加载ROM文件
        createMapper();//创建映射器
    }

    /**
     * 创建映射器
     */
    private void createMapper() {
        mapper = MapperFactory.createMapper(romHeader, this);
    }

    /**
     * 读取ROM的头信息Header
     * <p>
     * 可参考项目下的nes_rom格式.pdf
     *
     * @param raf
     * @throws IOException
     */
    private void loadHeader(RandomAccessFile raf) throws IOException {
        raf.seek(0);//回到最开始位置
        //先读取[0-3]个字节，每个ROM固定内容NES[sub]
        byte[] data = new byte[4];
        raf.read(data);
        char[] name = new char[4];
        for (int i = 0; i < data.length; i++) {
            name[i] = (char) data[i];
        }
        //[4]字节:PRG 块数量，一块大小为 16KB
        int prgRomChunks = raf.read();
        //[5]字节:CHR 块数量，一块大小为 8KB
        int chrRomChunks = raf.read();
        /*
            [6]字节:
            76543210
            ||||||||
            |||||||+- Mirroring: 0: 水平镜像（PPU 章节再介绍）
            |||||||              1: 垂直镜像（PPU 章节再介绍）
            ||||||+-- 1: 卡带上有没有带电池的 SRAM
            |||||+--- 1: Trainer 标志
            ||||+---- 1: 4-Screen 模式（PPU 章节再介绍）
            ++++----- Mapper 号的低 4 bit
         */
        int flag0 = raf.read();//mapper类型的低四位在该byte的高四位上
        boolean trainer = (flag0 & 0x04) > 0;
        mirror = (flag0 & 0x01) == 0 ? Mirror.HORIZONTAL : Mirror.VERTICAL;//镜像方式
        boolean sramEnable = (flag0&0x02)>0;

        /*
            [7]字节:
            76543210
            ||||||||
            |||||||+- VS Unisystem，不需要了解
            ||||||+-- PlayChoice-10，不需要了解
            ||||++--- 如果为 2，代表 NES 2.0 格式，不需要了解
            ++++----- Mapper 号的高 4 bit
         */
        int flag1 = raf.read();//mapper类型的高四位在该byte的高四位上
        int mapper = (flag1 & 0xF0) | (flag0 >> 4);
//        System.out.println("映射器类型:"+mapper+"   镜像模式:"+mirror + "   chr块:"+chrRomChunks);
        /*
         Mapper MSB/Submapper
         D~7654 3210
           ---------
           SSSS NNNN
           |||| ++++- Mapper number D11..D8
           ++++------ Submapper number
         */
        int flag2 = raf.read();

        /*
             PRG-ROM/CHR-ROM size MSB
             D~7654 3210
               ---------
               CCCC PPPP
               |||| ++++- PRG-ROM size MSB
               ++++------ CHR-ROM size MSB
         */
        int flag3 = raf.read();
        /*
            PRG-RAM/EEPROM size
            D~7654 3210
            ---------
            pppp PPPP
            |||| ++++- PRG-RAM (volatile) shift count
            ++++------ PRG-NVRAM/EEPROM (non-volatile) shift count
            If the shift count is zero, there is no PRG-(NV)RAM.
            If the shift count is non-zero, the actual size is
            "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7.
            如果移位计数为零，则不存在 PRG-(NV)RAM。
            如果移位计数不为零，则实际大小为
            "64 << 移位计数 "字节，即移位计数为 7 时为 8192 字节。
         */
        int flag4 = raf.read();

        /*
            CHR-RAM size
            D~7654 3210
            ---------
            cccc CCCC
            |||| ++++- CHR-RAM size (volatile) shift count
            ++++------ CHR-NVRAM size (non-volatile) shift count
            If the shift count is zero, there is no CHR-(NV)RAM.
            If the shift count is non-zero, the actual size is
            "64 << shift count" bytes, i.e. 8192 bytes for a shift count of 7.
         */
        int flag5 = raf.read();

        /*
             D~7654 3210
             ---------
             .... ..VV
                    ++- CPU/PPU timing mode
                         0: RP2C02 ("NTSC NES")
                         1: RP2C07 ("Licensed PAL NES")
                         2: Multiple-region
                         3: UA6538 ("Dendy")
         */
        int flag6 = raf.read();

        romHeader = new RomHeader(name, prgRomChunks, chrRomChunks, mapper, trainer, sramEnable);
    }

    /**
     * 读取ROM中的PrgROM
     *
     * @param raf
     */
    private void loadPrgROM(RandomAccessFile raf) throws IOException {
        raf.seek(RomHeader.HEADER_LEN + (romHeader.isTrainer() ? RomHeader.TRAINER_LEN : 0));
        //根据prg块信息，创建对应数组
        prgData = new byte[romHeader.getPrgRomChunks() * RomHeader.PRG_ROM_CHUCK_LEN];
        raf.read(prgData);//将rom中的prg部分读取到数组中
    }

    /**
     * 读取ROM中的ChrROM
     *
     * @param raf
     * @throws IOException
     */
    private void loadChrROM(RandomAccessFile raf) throws IOException {
        //移动到ROM的ChrROM位置，其实这句等于没动，因为上面loadPrgROM()执行后的位置就是该位置，这里是理解含义
        raf.seek(RomHeader.HEADER_LEN + (romHeader.isTrainer() ? RomHeader.TRAINER_LEN : 0) + romHeader.getPrgRomChunks() * RomHeader.PRG_ROM_CHUCK_LEN);
        //根据chr块信息，创建对应数组
//        chrData = new byte[romHeader.getChrRomChunks()==0?RomHeader.CHR_ROM_CHUCK_LEN:romHeader.getChrRomChunks()*RomHeader.CHR_ROM_CHUCK_LEN];
        chrData = new byte[romHeader.getChrRomChunks() * RomHeader.CHR_ROM_CHUCK_LEN];
        raf.read(chrData);//将rom中的chr部分读取到数组中
    }

    private void loadRom(File romFile) {
        try (RandomAccessFile raf = new RandomAccessFile(romFile, "r")) {
            loadHeader(raf);//读取ROM头信息
            loadPrgROM(raf);//读取PrgROM
            loadChrROM(raf);//读取ChrROM
            romFileName = romFile.getName();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public byte[] getPrgData() {
        return prgData;
    }

    public byte[] getChrData() {
        return chrData;
    }

    public RomHeader getRomHeader() {
        return romHeader;
    }

    public Mapper getMapper() {
        return mapper;
    }

    public Mirror getMirror() {
        return mirror;
    }

    public enum Mirror implements Serializable{
        HORIZONTAL,//水平镜像
        VERTICAL,//垂直镜像
        ONESCREEN_LO,
        ONESCREEN_HI
    }

    public String getRomFileName() {
        return romFileName;
    }
}
